﻿#if UNITY_2019_4_OR_NEWER
#define UNITY_3D
#endif

#if UNITY_EDITOR && PROFILING_CORE_API
#define PROFILER_MODULE
#endif

#if UNITY_3D && (UNITY_EDITOR || COMMAND_PROFILER)
#define PROFILING
#endif

using System.Collections;
using System.Collections.Generic;
using System.Threading;

#if UNITY_3D
using UnityEngine;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif

#if PROFILING
using UnityEngine.Profiling;
#endif

namespace Devil
{
    using IteratorStack = Stack<IEnumerator>;

    /// <summary>
    /// 线程实例
    /// </summary>
    public interface IThreadWorker 
    {
        ParallelDispatcher Dispatcher { get; }
    }

    internal interface ILoopWorker
    {
        float deltaTime { get; }
    }

    public interface ICommandHandler
    {
        ParallelDispatcher.Handler GetHandler();
    }

    public partial class ParallelDispatcher : System.IDisposable
    {
        //const float tick_2_millis = 0.0001f; // 1f / 10000f;
        // yield command stack size
#if UNITY_EDITOR
        static bool sPause;
        public static bool IsPaused => sPause;
#endif

        readonly EquatableDelegate<BaseCommand> mEqualitableCmd = (cmd1, cmd2) => cmd1 == null ? cmd2 == null : cmd1.id == cmd2.id;

        const int ITERATOR_STACK_SIZE = 8;
        public const double tick_2_seconds = 0.0000001d;
        public const double tick_2_milisecs =0.0001d;
        public const long second_2_ticks = 10000000;
        public const int TICK_MILLIS = 30;
        //static int sProcessorCount = 16;
        static ParallelDispatcher sSchedule;
        readonly static object sync_lock = new object();

        static System.Random sRandom;
        public static float GetRandomValue()
        {
            lock (sync_lock)
            {
                if (sRandom == null)
                    sRandom = new System.Random(ParallelUtils.GenUniqueID());
            }
            return (float)sRandom.NextDouble();
        }

        public static int GetRandomValue(int min, int max)
        {
            lock (sync_lock)
            {
                if (sRandom == null)
                    sRandom = new System.Random(ParallelUtils.GenUniqueID());
            }
            return sRandom.Next(min, max);
        }

        public static float GetRandomValue(float min, float max)
        {
            lock (sync_lock)
            {
                if (sRandom == null)
                    sRandom = new System.Random(ParallelUtils.GenUniqueID());
            }
            var v = sRandom.NextDouble();
            return (max - min) * (float)v + min;
        }

        public static bool IsValid => sSchedule != null && !sSchedule.mDisposed;

        public static ParallelDispatcher GetInstance()
        {
            //lock (sync_lock)
            {
                return sSchedule;
            }
        }

        public static bool IsInitialized
        {
            get
            {
                lock (sync_lock)
                {
                    return sSchedule != null && !sSchedule.mDisposed;
                }
            }
        }
        /// <summary>
        /// 初始化子线程数据(应该确保该方法在主线程执行)。
        /// </summary>
        public static ParallelDispatcher InitApplicationData()
        {
            //Initialize();

            lock (sync_lock)
            {
                if (/*!sIsInitialized ||*/ sSchedule == null || sSchedule.mDisposed)
                {
                    sSchedule = null;
#if THREAD_WORKERS_LIMITED
                    var processor = 4;
#elif UNITY_PS5
                    var processor = 12;
#elif UNITY_3D
                    var processor = SystemInfo.processorCount;
#else
                    // not unity 3d
                    var processor = System.Environment.ProcessorCount;
#endif
                    var processors = ParallelUtils.Max(8, processor - 1);
                    var loopers = ParallelUtils.Max(4, processor >> 1);
                    /*sSchedule = */
                    var inst = new ParallelDispatcher(128, processors, loopers);
                    //sStackPool = new ObjectPool<IteratorStack>(64, () => new IteratorStack(ITERATOR_STACK_SIZE));
                    return inst;
                }
#if UNITY_EDITOR
                else
                {
                    sSchedule.mMainThreadID = Thread.CurrentThread.ManagedThreadId;
                }
#endif
                return sSchedule;
            }
        }

        public static void QuitApplication()
        {
            ParallelDispatcher ct = null;
            lock (sync_lock)
            {
                ct = sSchedule;
                //sSchedule = null;
                if (ct != null)
                {
                    ct.Dispose();
                }
            }
        }

        //static MainWorker GetMainWorker()
        //{
        //    MainWorker worker = null;
        //    lock(sync_lock)
        //    {
        //        if (sSchedule != null)
        //            worker = sSchedule.mMainThreadWorker;
        //    }
        //    return worker;
        //}

#if UNITY_EDITOR
        public static void SetPause(bool pause)
        {
            if (pause != sPause)
            {
                sPause = pause;
                Debug.LogFormat("[Parallel] App was {0}", pause ? "Paused" : "Resumed");
            }
        }
#endif

//        // 主线程执行逻辑
//        public static void MainUpdate(float deltaTime)
//        {
//#if UNITY_EDITOR
//            if(sPause)
//                return;
//#endif
//            var worker = GetMainWorker();
//            if (worker != null)
//                worker.Update(deltaTime);
//        }

        public static bool IsMainThread
        {
            get
            {
                var schedule = sSchedule;
                return schedule != null && Thread.CurrentThread.ManagedThreadId == schedule.mMainThreadID;
            }
        }

        int mIdCtor;
        // 命令操作同步锁
        readonly object mLock = new object();

        ObjectPool<IteratorStack> mStackPool;
        // standy commands
        CascadeList<BaseCommand> mCommands;
        // execute commands
        CascadeList<BaseCommand> mMainCmdQueue;
        // sub execute index
        int mSubExecIndex;
        // sub executes
        CascadeList<BaseCommand> mSubCmdQueue;
        ThreadFlag mDispatchingState;
        HashSet<int> mRecursiveCheck;
        Stack<BaseCommand> mDependentCheck;
        ThreadWorker[] mWorkers;
        LoopWorker[] mLoopers;
        MainWorker mMainThreadWorker;
        int mActiveWorkers;
        bool mDisposed;
        int mMainThreadID;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
        BaseCommand.Buffer<SubCmd, ICommand, bool> mSubCmdsBuffer;
        BaseCommand.Buffer<YieldCmd, IYieldCommand, bool> mYieldCmdsBuffer;
        BaseCommand.Buffer<WaitCmd, ICommand, bool> mWaitCmdsBuffer;
        BaseCommand.Buffer<ParallelIndex, ICommandParallelFor<int>, int> mParallelCmdsBuffer;
        int mLockBuffer;
        internal void GetBufferLock()
        {
            Interlocked.Increment(ref mLockBuffer);
        }
        internal void ReleaseBufferLock()
        {
            Interlocked.Decrement(ref mLockBuffer);
        }
        internal bool IsBufferLocked => mLockBuffer > 0;
#endif

        int NextID()
        {
            var id = ++mIdCtor;
            if (id == 0)
            {
                // regenerate id
                id = ++mIdCtor;
                Debug.LogError("ID loops ++");
            }
            return id;
        }

        // 确保在主线程构造该对象
        private ParallelDispatcher(int initComands, int initThreadWorkers, int loopers)
        {
            mMainThreadID = Thread.CurrentThread.ManagedThreadId;
            EquatableDelegate<ILoopCommand> eqLooper = (a, b) => a == null ? (b == null) : a.Equals(b);
            mStackPool = new ObjectPool<IteratorStack>(64, () => new IteratorStack(ITERATOR_STACK_SIZE));
            mCommands = new CascadeList<BaseCommand>(initComands, mEqualitableCmd);// new RandomList<BaseCommand>(initComands, eqCmd);
            mSubCmdQueue = new CascadeList<BaseCommand>(initComands, mEqualitableCmd);
            mMainCmdQueue = new CascadeList<BaseCommand>(initComands, mEqualitableCmd);
            //mDispatcher = DispatchCommands;
            mRecursiveCheck = new HashSet<int>();
            mDependentCheck = new Stack<BaseCommand>(8);
            //mScheduleBuffer = new RandomList<CommandSchedule>(initComands, (a, b) => a.cmd.id == b.cmd.id);

            mMainThreadWorker = new MainWorker(this, 64);
            //mLooper = new LoopWorker(this);
            mLoopers = new LoopWorker[loopers];
            for (int i = 0; i < loopers; i++)
            {
                mLoopers[i] = new LoopWorker(this, i);
            }
            mWorkers = new ThreadWorker[initThreadWorkers];
            for (int i = 0; i < initThreadWorkers; i++)
            {
                mWorkers[i] = new ThreadWorker(this, i);
            }

#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            mSubCmdsBuffer = new SubCmd.Buffer(32);
            mYieldCmdsBuffer = new YieldCmd.Buffer(32);
            mWaitCmdsBuffer = new WaitCmd.Buffer(64);
            mParallelCmdsBuffer = new ParallelIndex.Buffer(32);
#endif

            mDispatchingState = new ThreadFlag(
#if UNITY_EDITOR
                "dispatch command"
#endif
                );
            lock (sync_lock)
            {
                if (sSchedule != null)
                    sSchedule.Dispose();
                sSchedule = this;
            }
            Debug.LogFormat("[Parallel] ParallelDispatcher was initialized with {0} thread workers.", initThreadWorkers);
            mDispatchingState.Start(DispatchCommands);
        }

        public void MainUpdate(float deltaTime)
        {
#if UNITY_EDITOR
            if (sPause)
                return;
#endif
            if (!mDisposed)
                mMainThreadWorker.Update(deltaTime);
        }

        public Handler Schedule(ICommand command, bool mainThread, Handler dependency)
        {
            if (command == null)
                throw new System.NullReferenceException("command is null.");
            lock (mLock)
            {
                if (mDisposed)
                    throw new System.InvalidOperationException("parallel dispatchere was disposed.");
                var id = NextID();
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                BaseCommand exec = mSubCmdsBuffer.Get(id, command, mainThread, dependency.cmd);
#else
                BaseCommand exec = new SubCmd(id, mainThread, command, dependency.cmd);
#endif
                mCommands.Add(exec);

#if PROFILER_MODULE
                CountScheduleCommands();
#endif

                return new Handler(this, exec);
            }
        }

        public Handler Schedule(IYieldCommand command, bool mainThread, Handler dependency)
        {
            if (command == null)
                throw new System.NullReferenceException("command is null.");
            lock (mLock)
            {
                if (mDisposed)
                    throw new System.InvalidOperationException("parallel dispatchere was disposed.");
                var id = NextID();
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                BaseCommand exec = mYieldCmdsBuffer.Get(id, command, mainThread, dependency.cmd);
#else
                BaseCommand exec = new YieldCmd(id, command, mainThread, dependency.cmd);
#endif
                mCommands.Add(exec);
                return new Handler(this, exec);
            }
        }

        public Handler Schedule(ILoopCommand command, bool mainThread, Handler dependency)
        {
            if (command == null)
                throw new System.NullReferenceException("command is null.");
            lock (mLock)
            {
                if (mDisposed)
                    throw new System.InvalidOperationException("parallel dispatchere was disposed.");
                var id = NextID();
                BaseCommand exec = new LoopCmd(id, command, mainThread, dependency.cmd);
                mCommands.Add(exec);
#if PROFILER_MODULE
                CountScheduleCommands();
#endif
                return new Handler(this, exec);
            }
        }

        public Handler Schedule<T>(ICommandParallelFor<T> command, IEnumerable enumerable, Handler dependency)
        {
            if (command == null)
                throw new System.NullReferenceException("command is null.");
            if (enumerable == null)
                return default;
            lock (mLock)
            {
                if (mDisposed)
                    throw new System.InvalidOperationException("parallel dispatchere was disposed.");
                var id = NextID();
                BaseCommand exec = new ParallelCmd<T>(id, command, enumerable, dependency.cmd);
                mCommands.Add(exec);
#if PROFILER_MODULE
                CountScheduleCommands();
#endif
                return new Handler(this, exec);
            }
        }

        public Handler Schedule(ICommandParallelFor<int> command, int count, Handler dependency)
        {
            if (command == null)
                throw new System.NullReferenceException("command is null.");
            if (count <= 0)
                return default;
            lock (mLock)
            {
                if (mDisposed)
                    throw new System.InvalidOperationException("parallel dispatchere was disposed.");
                var id = NextID();
                BaseCommand exec = new ParallelIndex(id, count, command, dependency.cmd);
                mCommands.Add(exec);
#if PROFILER_MODULE
                CountScheduleCommands();
#endif
                return new Handler(this, exec);
            }
        }

        bool IsDependentOn(BaseCommand basecmd, BaseCommand dependent)
        {
            if (basecmd == dependent || basecmd.dependentOn == null)
                return false;
            lock (mDependentCheck)
            {
                mDependentCheck.Clear();
                mRecursiveCheck.Clear();
                mRecursiveCheck.Add(basecmd.id);
                mDependentCheck.Push(basecmd);
                while (mDependentCheck.Count > 0)
                {
                    var cmd = mDependentCheck.Pop();
                    var next = cmd.dependentOn;
                    if (next == dependent)
                        return true;
                    if (next != null && !next.isComplete && mRecursiveCheck.Add(next.id))
                    {
                        mDependentCheck.Push(next);
                    }
                    var wait = cmd as WaitCmd;
                    if (wait != null && wait.mWaitTargets != null)
                    {
                        lock (wait.GetSyncLock())
                        {
                            for (int i = wait.mWaitTargets.Count - 1; i >= 0; i--)
                            {
                                next = wait.mWaitTargets[i].cmd;
                                if (next == null || next.isComplete)
                                    wait.mWaitTargets.RemoveAt(i);
                                else if (next == dependent)
                                    return true;
                                else if (mRecursiveCheck.Add(next.id))
                                    mDependentCheck.Push(next);
                            }
                        }
                    }
                }
            }
            return false;
        }

        void GetDependentExecuteProgress(BaseCommand baseCmd, ref float progress, ref float totalProgress)
        {
            lock (mDependentCheck)
            {
                mDependentCheck.Clear();
                mRecursiveCheck.Clear();
                mRecursiveCheck.Add(baseCmd.id);
                mDependentCheck.Push(baseCmd);
                while (mDependentCheck.Count > 0)
                {
                    var cmd = mDependentCheck.Pop();
                    var next = cmd.dependentOn;
                    if (next != null && mRecursiveCheck.Add(next.id))
                        mDependentCheck.Push(next);
                    var wait = cmd as WaitCmd;
                    if (wait == null)
                    {
                        var f = cmd.GetExecutionProgress(this);
                        if (cmd.GetCommandInstance() is IBlendableProgress blend)
                        {
                            var weight = blend.BlendWeight;
                            progress += f * weight;
                            totalProgress += weight;
                        }
                        else
                        {
                            progress += f;
                            totalProgress += 1;
                        }
                    }
                    else if (wait.mWaitTargets != null)
                    {
                        lock (wait.GetSyncLock())
                        {
                            for (int i = wait.mWaitTargets.Count - 1; i >= 0; i--)
                            {
                                next = wait.mWaitTargets[i].cmd;
                                if (next != null && mRecursiveCheck.Add(next.id))
                                    mDependentCheck.Push(next);
                            }
                        }
                    }
                }
            }
        }

        internal void Abort(BaseCommand cmd, bool byDependency, bool includeDependents)
        {
            if (cmd == null || mDisposed)
                return;
            //bool push, abort;
            if (includeDependents)
            {
                var lst = new List<BaseCommand>();
                lock (mDependentCheck)
                {
                    mDependentCheck.Clear();
                    mRecursiveCheck.Clear();
                    if (!cmd.isComplete)
                    {
                        mDependentCheck.Push(cmd);
                        mRecursiveCheck.Add(cmd.id);
                        if (cmd.IsBreakable(byDependency))
                        {
                            lst.Add(cmd);
                        }
                    }
                    while (mDependentCheck.Count > 0)
                    {
                        var it = mDependentCheck.Pop();
                        var dependent = it.dependentOn;
                        if (dependent != null && !dependent.isComplete)
                        {
                            if (mRecursiveCheck.Add(dependent.id))
                                mDependentCheck.Push(dependent);
                            if (dependent.IsBreakable(true))
                                lst.Add(dependent);
                        }
                        var wait = it as WaitCmd;
                        if (wait != null && wait.mWaitTargets != null)
                        {
                            lock (wait.GetSyncLock())
                            {
                                for (int i = wait.mWaitTargets.Count - 1; i >= 0; i--)
                                {
                                    it = wait.mWaitTargets[i].cmd;
                                    if (it == null || it.isComplete)
                                    {
                                        wait.mWaitTargets.RemoveAt(i);
                                        continue;
                                    }
                                    if (mRecursiveCheck.Add(it.id))
                                        mDependentCheck.Push(it);
                                    if (it.IsBreakable(true))
                                    {
                                        lst.Add(it);
                                    }
                                }
                            }
                        }
                    }
                }
                if (lst.Count > 0)
                {
                    lock (mLock)
                    {
                        for (int i = 0; i < lst.Count; i++)
                        {
                            mCommands.Remove(lst[i]);
                        }
                    }
                    for (int i = 0; i < lst.Count; i++)
                    {
                        lst[i].OnBreak(this);
                    }
                }
            }
            else
            {
                if (cmd.isComplete || !cmd.IsBreakable(byDependency))
                    return;
                lock (mLock)
                {
                    mCommands.Remove(cmd);
                }
                cmd.OnBreak(this);
            }
#if PROFILER_MODULE
            CountScheduleCommands();
#endif
        }

        // 任务分配线程
        void DispatchCommands(object state)
        {
            var threadid = Thread.CurrentThread.ManagedThreadId;
#if UNITY_EDITOR
            //RTLog.LogFormat(LogCat.Editor, "Start Dispatch Command @Thread[{0}]", threadid);
#endif
            var flag = state as ThreadFlag;
            int dispatchIndex = 0;
            int sleepMillis = ParallelUtils.Max(10, TICK_MILLIS >> 2);
            while (flag != null && !flag.IsAbort)
            {
                //BaseCommand exec = null;

#if UNITY_EDITOR
                if (sPause)
                {
                    Thread.Sleep(TICK_MILLIS);
                    continue;
                }
#endif
                int num = 0;
                lock (mLock)
                {
                    if (dispatchIndex >= mCommands.Count)
                        dispatchIndex = 0;
                    while (dispatchIndex < mCommands.Count)
                    {
                        var cmd = mCommands[dispatchIndex];
                        if (!cmd.IsReady)
                        {
                            dispatchIndex++;
                            continue;
                        }
                        num++;
                        mCommands.RemoveAt(dispatchIndex);
                        //exec = cmd;
                        if (!cmd.isComplete)
                        {
                            cmd.Schedule(this);
                            //break;
                        }
                    }
                }
#if PROFILER_MODULE
                CountScheduleCommands();
                CountSubCommands();
#endif
                if (num > 0)
                    Thread.Yield();
                else
                    Thread.Sleep(sleepMillis);
#if !KEEP_PARALLEL_THREAD_ALIVE
                lock (mSubCmdQueue)
                {
                    if (mSubCmdQueue.Count == 0 && mActiveWorkers > 0)
                    {
                        for (int i = mActiveWorkers - 1; i >= 0; i--)
                        {
                            var worker = mWorkers[i];
                            if (worker.ActiveCount == 0)
                            {
                                worker.Stop();
                                mActiveWorkers--;
                                if (i != mActiveWorkers)
                                {
                                    mWorkers[i] = mWorkers[mActiveWorkers];
                                    mWorkers[mActiveWorkers] = worker;
                                }
                            }
                        }
                    }
                }
#endif
            }
#if UNITY_EDITOR
            //RTLog.LogFormat(LogCat.Editor, "Dispatch Command @Thread[{0}] Stoped.", threadid);
#endif
        }

        public void Dispose()
        {
            lock (mLock)
            {
                if (mDisposed)
                    return;
                mDisposed = true;
            }
            if (mDispatchingState != null)
                mDispatchingState.Abort();
            mDispatchingState = null;
            //if (mLooper.threadFlag != null)
            //    mLooper.threadFlag.Abort();
            for (int i = mCommands.Count - 1; i >= 0; i--)
            {
                var cmd = mCommands[i];
                if (!cmd.isComplete)
                    cmd.OnBreak(this);
            }
            for (int i = mSubCmdQueue.Count - 1; i >= 0; i--)
            {
                var cmd = mSubCmdQueue[i];
                if (!cmd.isComplete)
                {
                    cmd.OnBreak(this);
                }
            }
            for (int i = mMainCmdQueue.Count - 1; i >= 0; i--)
            {
                var cmd = mMainCmdQueue[i];
                if (!cmd.isComplete)
                {
                    cmd.OnBreak(this);
                }
            }
            for (int i = 0; i < mLoopers.Length; i++)
            {
                mLoopers[i].Dispose();
            }
            for (int i = 0; i < mWorkers.Length; i++)
            {
                mWorkers[i].Dispose();
            }
            mMainThreadWorker.Dispose();
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            mSubCmdsBuffer?.Release();
            mYieldCmdsBuffer?.Release();
            mWaitCmdsBuffer?.Release();
            mParallelCmdsBuffer?.Release();
            mSubCmdsBuffer = null;
            mYieldCmdsBuffer = null;
            mWaitCmdsBuffer = null;
            mParallelCmdsBuffer = null;
#endif
            mActiveWorkers = 0;
            mMainCmdQueue.Clear();
            mSubCmdQueue.Clear();
            mCommands.Clear();
            Debug.LogFormat("[Parallel] ParallelDispatcher was disposed.");
        }

        ~ParallelDispatcher()
        {
            Dispose();
        }

        LoopWorker GetLoopWorker()
        {
            LoopWorker worker = null;
            for (int i = mLoopers.Length - 1; i >= 0; i--)
            {
                if (worker == null || worker.ActiveCount > mLoopers[i].ActiveCount)
                    worker = mLoopers[i];
            }
            return worker;
        }

        // 同步锁(mSubCmdQueue)中执行
        ThreadWorker StartWorker()
        {
            if (mActiveWorkers < mWorkers.Length)
            {
                var worker = mWorkers[mActiveWorkers];
                var start = worker.Start();
                if (!start)
                    return null;
                mActiveWorkers++;
                return worker;
            }
            return null;
        }

        public string GetCommandsInfo()
        {
            var buf = ParallelUtils.GetBuilder();
            MinHeap<BaseCommand> cmds = new MinHeap<BaseCommand>(mCommands.Count + mSubCmdQueue.Count + mMainCmdQueue.Count,
                (a, b) =>
                {
                    if (a.IsReady)
                        return -1;
                    if (b.IsReady)
                        return 1;
                    if (IsDependentOn(a, b))
                        return 1;
                    return -11;
                });
            string[] states = { "## STANDBY SEQUENCE ##", "## EXECUTING ##", "## COMPLETE ##" };
            int stat = -1;
            lock (mLock)
            {
                mMainThreadWorker.GetExecutingCmds(cmds);
                //mLooper.GetExecutingCmds(cmds);
                for (int i = 0; i < mLoopers.Length; i++)
                {
                    mLoopers[i].GetExecutingCmds(cmds);
                }
                for (int i = 0; i < mActiveWorkers; i++)
                {
                    mWorkers[i].GetExecutingCmds(cmds);
                }
                for (int i = 0; i < mMainCmdQueue.Count; i++)
                {
                    cmds.Add(mMainCmdQueue[i]);
                }
                for (int i = 0; i < mSubCmdQueue.Count; i++)
                {
                    cmds.Add(mSubCmdQueue[i]);
                }
                for (int i = 0; i < mCommands.Count; i++)
                {
                    cmds.Add(mCommands[i]);
                }
                while (cmds.Count > 0)
                {
                    var cmd = cmds.RemoveTop();
                    var exec = cmd.isComplete ? 2 : (cmd.IsReady ? 1 : 0);
                    if (exec != stat)
                    {
                        stat = exec;
                        buf.Append(states[exec]).NextLine();
                    }
                    buf.Append('<').Append(cmd.id);
                    if (cmd is LoopCmd)
                        buf.Append("> [LOOP] \"");
                    else
                        buf.Append("> \"");
                    buf.Append(cmd.ToString()).Append('\"');
                    var dependent = cmd.dependentOn;
                    if (stat == 0 && dependent != null)
                    {
                        buf.Append("    [dependent on: ").Append(dependent.id);
                        if (cmd is WaitCmd)
                        {
                            var lst = ((WaitCmd)cmd).mWaitTargets;
                            if (lst != null)
                            {
                                for (int i = 0; i < lst.Count; i++)
                                {
                                    var prev = lst[i].cmd;
                                    if (prev != null && !prev.isComplete)
                                        buf.Append(" ,").Append(prev.id);
                                }
                            }
                        }
                        buf.Append("]").NextLine();
                    }
                    else if (stat == 1)
                    {
                        float progress = 0;
                        float num = 0;
                        GetDependentExecuteProgress(cmd, ref progress, ref num);
                        buf.Append("    (").Append(num > 0 ? (progress / num).ToString("0.##%") : "0%").Append(")").NextLine();
                    }
                    buf.NextLine();
                }
            }
            if (buf.Length == 0)
                buf.Append("## NONE OF COMMAND ##");
            return ParallelUtils.ReturnRelease(buf);
        }

        internal abstract class Worker : IThreadWorker
        {
            internal readonly ParallelDispatcher _dispatcher;
            //internal readonly object workLock = new object();
            internal abstract int ActiveCount { get; }
            public ParallelDispatcher Dispatcher => _dispatcher;

            internal Worker(ParallelDispatcher schedule)
            {
                this._dispatcher = schedule;
            }

            internal abstract void GetExecutingCmds(ICollection<BaseCommand> col);

            internal int RemoveCmd(CascadeList<BaseCommand> lst, BaseCommand cmd, int starti)
            {
                for (int i = starti < lst.Count ? starti : -1; i >= 0; i--)
                {
                    if (lst[i] == cmd)
                    {
                        lst.RemoveAt(i);
                        return i;
                    }
                }
                for (int i = lst.Count - 1; i > starti; i--)
                {
                    if (lst[i] == cmd)
                    {
                        lst.RemoveAt(i);
                        return i;
                    }
                }
                return -1;
            }

            internal abstract void Dispose();

        }

        internal class MainWorker : Worker, ILoopWorker
        {
            //int mBatchNum;
            CascadeList<BaseCommand> mWaitFlags;
            BaseCommand[] mBatch;
            public float deltaTime { get; private set; }
            internal override int ActiveCount => mWaitFlags.Count;
            internal MainWorker(ParallelDispatcher schedule, int batchNum) : base(schedule)
            {
                mBatch = new BaseCommand[ParallelUtils.Max(32, batchNum)];
                mWaitFlags = new CascadeList<BaseCommand>(64, schedule.mEqualitableCmd);
            }

            internal override void GetExecutingCmds(ICollection<BaseCommand> col)
            {
                for (int i = mWaitFlags.Count - 1; i >= 0; i--)
                {
                    var cmd = mWaitFlags[i];
                    if (cmd != null && !cmd.isComplete)
                        col.Add(cmd);
                }
            }

            internal void Update(float deltaTime)
            {
                BaseCommand current;
                this.deltaTime = deltaTime;
                int exec = 0;
                int execi;
                lock (_dispatcher.mMainCmdQueue)
                {
                    while (_dispatcher.mMainCmdQueue.Count > 0 && exec < mBatch.Length)
                    {
                        execi = _dispatcher.mMainCmdQueue.Count - 1;
                        current = _dispatcher.mMainCmdQueue.RemoveAt(execi);
                        mBatch[exec++] = current;
                    }
                }
                for (int i = 0; i < exec; i++)
                {
                    current = mBatch[i];
                    mBatch[i] = null;
                    current.Execute(this);
                    if (!current.isComplete)
                        mWaitFlags.Add(current);
                }
                for (int i = mWaitFlags.Count - 1; i >= 0; i--)
                {
                    current = mWaitFlags[i];
                    current.Execute(this);
                    if (current.isComplete)
                        mWaitFlags.RemoveAt(i);
                }
#if PROFILER_MODULE
                _dispatcher.CountMainCommands();
#endif
            }

            internal override void Dispose()
            {
                var flags = Interlocked.Exchange(ref mWaitFlags, null);
                if (flags != null)
                {
                    for (int i = flags.Count - 1; i >= 0; i--)
                    {
                        if (!flags[i].isComplete)
                            flags[i].OnBreak(_dispatcher);
                    }
                    flags.Clear();
                }
            }
        }

        internal class ThreadWorker : Worker
        {
            public readonly int id;
            internal readonly WaitCallback exectutor;
            //internal int threadId;
            //internal int workerId;
            ThreadFlag threadFlag;
            BaseCommand current;
            //int currentIndex;
            //readonly object _lock = new object();
            internal ThreadWorker(ParallelDispatcher schedule, int id) : base(schedule)
            {
                this.id = id;
                //workerId = -1;
                exectutor = Execute;
                //mWaitFlags = new CascadeList<BaseCommand>(16, (a, b) => a.id == b.id);
            }

            internal override int ActiveCount
            {
                get
                {
                    var cmd = current;
                    return cmd == null || cmd.isComplete ? 0 : 1;
                }
            }

            internal bool Start()
            {
                if(threadFlag == null || threadFlag.IsAbort)
                {
                    threadFlag = new ThreadFlag(
//#if UNITY_EDITOR
//                        $"tworker_{id}"
//#endif
                        );
                    threadFlag.Start(exectutor);
                }
                return threadFlag != null && !threadFlag.IsAbort;
            }

            internal void Stop()
            {
                threadFlag?.Abort();
            }

            internal override void Dispose()
            {
                threadFlag?.Abort();
                var cmd = Interlocked.Exchange(ref current, null);
                if (cmd != null && !cmd.isComplete)
                    cmd.OnBreak(_dispatcher);
            }

            internal override void GetExecutingCmds(ICollection<BaseCommand> col)
            {
                var cmd = current;
                if (cmd != null && !cmd.isComplete)
                    col.Add(cmd);
            }

            void Execute(object state)
            {
                //threadId = Thread.CurrentThread.ManagedThreadId;
                var flag = state as ThreadFlag;
                //int batch = 32;
                while (!flag.IsAbort)
                {
#if UNITY_EDITOR
                    if (sPause)
                    {
                        Thread.Sleep(TICK_MILLIS);
                        continue;
                    }
#endif
                    int execi = 0;
                    lock (_dispatcher.mSubCmdQueue)
                    {
                        if (_dispatcher.mSubCmdQueue.Count > 0)
                        {
                            _dispatcher.mSubExecIndex = (_dispatcher.mSubExecIndex + 1) % _dispatcher.mSubCmdQueue.Count;
                            execi = _dispatcher.mSubExecIndex;
                            current = _dispatcher.mSubCmdQueue[execi];
                            if (current.executeThread != ExecuteThread.Parallel)
                                _dispatcher.mSubCmdQueue.RemoveAt(execi);
                        }
                        else
                        {
                            current = null;
                        }
                    }

                    if (current != null)
                    {
                        current.Execute(this);
                        var complete = current.isComplete;
                        if (!complete && current.executeThread != ExecuteThread.Parallel)
                        {
                            var worker = _dispatcher.GetLoopWorker();
                            worker.Join(current);
                        }
                        else if (complete && current.executeThread == ExecuteThread.Parallel)
                        {
                            lock (_dispatcher.mSubCmdQueue)
                            {
                                RemoveCmd(_dispatcher.mSubCmdQueue, current, execi);
                            }
                        }
                        current = null;
                        Thread.Yield();
                    }
                    else
                    {
                        Thread.Sleep(TICK_MILLIS);
                        //lock (schedule.mSubCmdQueue)
                        //{
                        //    if (schedule.mSubCmdQueue.Count == 0)
                        //        schedule.StopWorker(this);
                        //}
                    }
                }
            }

        }

        internal class LoopWorker : Worker, ILoopWorker
        {
            int mId;
            public float deltaTime { get; private set; }
            ThreadFlag threadFlag;
            //readonly object workLock = new object();
            internal readonly WaitCallback executor;
            CascadeList<BaseCommand> mWaitFlags;
            CascadeList<BaseCommand> mJoinBuffer;
            internal override int ActiveCount => mWaitFlags.Count + mJoinBuffer.Count;
            public LoopWorker(ParallelDispatcher dispatcher, int id) : base(dispatcher) 
            {
                mId = id;
                executor = Execute;
                mWaitFlags = new CascadeList<BaseCommand>(32, _dispatcher.mEqualitableCmd);
                mJoinBuffer = new CascadeList<BaseCommand>(8, _dispatcher.mEqualitableCmd);
            }

            internal override void GetExecutingCmds(ICollection<BaseCommand> col)
            {
                for (int i = mWaitFlags.Count - 1; i >= 0; i--)
                {
                    var cmd = mWaitFlags[i];
                    if (cmd != null && !cmd.isComplete)
                        col.Add(cmd);
                }
                for (int i = mJoinBuffer.Count - 1; i >= 0; i--)
                {
                    var cmd = mJoinBuffer[i];
                    if (cmd != null && !cmd.isComplete)
                        col.Add(cmd);
                }
            }

            internal void Join(BaseCommand cmd)
            {
                lock (mJoinBuffer)
                {
                    mJoinBuffer.Add(cmd);
                    if (threadFlag == null || threadFlag.IsAbort)
                    {
                        threadFlag = new ThreadFlag(
//#if UNITY_EDITOR
//                            $"looper_{mId}"
//#endif
                            );
                        threadFlag.Start(executor);
                    }
                }
            }

            internal override void Dispose()
            {
                threadFlag?.Abort();
                var flags = Interlocked.Exchange(ref mWaitFlags, null);
                if (flags != null)
                {
                    for (int i = flags.Count - 1; i >= 0; i--)
                    {
                        var cmd = flags[i];
                        if (!cmd.isComplete)
                            cmd.OnBreak(_dispatcher);
                    }
                    flags.Clear();
                }
                flags = Interlocked.Exchange(ref mJoinBuffer, null);
                if (flags != null)
                {
                    for (int i = flags.Count - 1; i >= 0; i--)
                    {
                        var cmd = flags[i];
                        if (!cmd.isComplete)
                            cmd.OnBreak(_dispatcher);
                    }
                    flags.Clear();
                }
            }

            void Execute(object state)
            {
                var ticks = TICK_MILLIS * 10000;
                var flag = state as ThreadFlag;
                //var watch = new System.Diagnostics.Stopwatch();
                //watch.Start();
                long lastTick = System.DateTime.UtcNow.Ticks;// watch.ElapsedTicks;
                Thread.Sleep(TICK_MILLIS);
                // fix 预测线程唤醒时间，以轻微修正
                //int fix = (int)(System.DateTime.UtcNow.Ticks - lastTick - ticks);
                //if (fix < 0)
                //    fix = 0;
                int hot = 10;
                while (flag != null && !flag.IsAbort)
                {
                    lock (mJoinBuffer)
                    {
                        for (int i = 0; i < mJoinBuffer.Count; i++)
                        {
                            mWaitFlags.Add(mJoinBuffer[i]);
                        }
                        mJoinBuffer.Clear();
                    }
                    if (mWaitFlags.Count == 0)
                    {
                        if (hot > 0)
                        {
                            hot--;
                        }
                        else
                        {
                            flag.Abort();
                            break;
                        }
                    }
                    else
                    {
                        hot = 0;
                    }
                    var t = System.DateTime.UtcNow.Ticks;
                    var delta = t - lastTick;
                    lastTick = t;
                    deltaTime = (float)(delta * tick_2_seconds);
                    //int exec = 0;
#if UNITY_EDITOR
                    if (!sPause)
                    {
#endif
                        for (int i = mWaitFlags.Count - 1; i >= 0; i--)
                        {
                            var cmd = mWaitFlags[i];

                            cmd.Execute(this);
                            if (cmd.isComplete)
                                mWaitFlags.RemoveAt(i);
                        }
#if UNITY_EDITOR
                    }
#endif
                    t = System.DateTime.UtcNow.Ticks;
                    var sleep = (int)(lastTick + ticks + ticks - delta - t);
                    if (sleep >= 10000)
                        Thread.Sleep(sleep / 10000);
                    else
                        Thread.Yield();


                }
            }
        }

        /// <summary>
        /// 多线程任务执行句柄，用以查询任务执行状态
        /// </summary>
        public struct Handler : ICommandHandler, System.IEquatable<Handler>
        {
            BaseCommand _cmd;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            int _id;
            internal BaseCommand cmd => _cmd != null && _id == _cmd.id ? _cmd : null;
#else
            internal BaseCommand cmd => _cmd;
#endif
            ParallelDispatcher schedule;
            /// <summary>
            /// 设置为独立命令（独立命令将不会由依赖任务中断）
            /// </summary>
            public bool IsIndependent
            {
                get
                {
                    var inst = cmd;
                    return inst != null && inst.isIndependent;
                }
                set
                {
                    var inst = cmd;
                    if (inst != null)
                        inst.isIndependent = value;
                }
            }
            internal Handler(ParallelDispatcher schedule, BaseCommand cmd)
            {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                this._id = cmd == null ? 0 : cmd.id;
                this._cmd = _id == 0 ? null : cmd;
#else
                this._cmd = cmd;
#endif
                this.schedule = schedule;
                // test time
                //SampleExecuteTime();
            }

            public bool Equals(Handler other)
            {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                return _id == other._id && _cmd == other._cmd && schedule == other.schedule;
#else
                return _cmd == other._cmd && schedule == other.schedule;
#endif
            }

            public override int GetHashCode()
            {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                return _id;
#else
                return _cmd == null ? 0 : _cmd.id;
#endif
            }

            internal bool IsDependentOn(BaseCommand dependent)
            {
                var inst = cmd;
                if (schedule == null || inst == null || inst.isComplete)
                    return false;
                lock (schedule.mLock)
                {
                    return schedule.IsDependentOn(inst, dependent);
                }
            }

            internal int GetExecutableId(ParallelDispatcher threading)
            {
                var inst = cmd;
                if (threading == schedule && inst != null && !inst.isComplete)
                    return inst.id;
                else
                    return 0;
            }

            /// <summary>
            /// 任务是否为可执行状态
            /// </summary>
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            public bool IsAlive { get { return _cmd != null && _cmd.id == _id && !_cmd.isComplete && !schedule.mDisposed; } }
#else
            public bool IsAlive { get { return _cmd != null && !_cmd.isComplete && !schedule.mDisposed; } }
#endif

            [System.Obsolete("该属性将不再支持，请使用 IsAlive 属性替换该属性。", false)]
            public bool IsValid => IsAlive;

            public bool IsNull { get { return cmd == null; } }

            /// <summary>
            /// 任务对象是否与给定的对象相同
            /// </summary>
            /// <param name="command"></param>
            /// <returns></returns>
            public bool IsCommand(object command)
            {
                var inst = cmd?.GetCommandInstance(); 
                return inst == null ? command == null : inst.Equals(command);
            }

            /// <summary>
            /// 返回（或将要）执行的任务对象
            /// </summary>
            /// <returns></returns>
            public object GetCommand() { return cmd?.GetCommandInstance(); }

            /// <summary>
            /// 中断任务执行
            /// </summary>
            /// <param name="includeDependents"></param>
            public void Abort(bool includeDependents = false)
            {
                if (schedule != null)
                {
                    schedule.Abort(cmd, false, includeDependents);
                }
            }

            /// <summary>
            /// 获取任务详细执行状态
            /// </summary>
            /// <returns></returns>
            public Execution GetExecution()
            {
                Execution exec;
                var inst = cmd;
                if (inst == null || schedule == null || inst.isComplete)
                {
                    exec.state = EExecutionState.Complete;
                    exec.progress = 1f;
                }
                else
                {
                    exec.progress = 0;
                    exec.state = inst.IsReady ? EExecutionState.Executing : EExecutionState.Standby;
                    if (exec.state == EExecutionState.Executing || inst is WaitCmd)
                    {
                        float progress = 0;
                        float num = 0;
                        schedule.GetDependentExecuteProgress(inst, ref progress, ref num);
                        exec.progress = num > 0 ? (progress / (float)num) : 0;
                    }
                }
                return exec;
            }

            /// <summary>
            /// 等待任务执行完成，该方法会阻塞线程，请谨慎使用（为了游戏体验，该方法不能被主线程调用），
            /// 如非必要，应当使用任务依赖系统或 Yield Command 系统来实现类似的功能
            /// </summary>
            /// <exception cref="System.Exception"></exception>
            public void WaitComplete()
            {
                if (_cmd == null || schedule == null || _cmd.isComplete)
                    return;

                if (schedule.mMainThreadID == Thread.CurrentThread.ManagedThreadId)
                    throw new System.Exception("Parallel.Wait don't allowed to be invoked on main thread.");

#if PROFILING
                Profiler.BeginSample(cmd.ToString());
#endif
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                while(_id == _cmd.id && !_cmd.isComplete)
#else
                while (!_cmd.isComplete)
#endif
                {
                    Thread.Sleep(TICK_MILLIS);
                }
#if PROFILING
                Profiler.EndSample();
#endif
            }

            Handler ICommandHandler.GetHandler()
            {
                return this;
            }

            public void SampleExecuteTime(string sampleName = null)
            {
#if PROFILING
                var inst = cmd;
                if (inst != null)
                    inst.BeginSample(string.IsNullOrEmpty(sampleName) ? "Parallel" : sampleName);
                //else
                //    Debug.LogWarning("invalid parallel handler.");
#endif
            }

            public override string ToString()
            {
                var inst = cmd;
                return inst == null ? "invaild cmd" : inst.ToString();
            }

        }

        /// <summary>
        /// 组合任务依赖句柄
        /// </summary>
        public struct CombinedHandler : ICommandHandler
        {
            string info;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            int _id;
#endif
            WaitCmd _cmd;
            ParallelDispatcher _dispatcher;

#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
            public bool IsAlive { get { return _cmd != null && _cmd.id == _id && !_cmd.isComplete; } }
#else
            public bool IsAlive { get { return _cmd != null && !_cmd.isComplete; } }
#endif

            [System.Obsolete("该属性将不再支持，请使用 IsAlive 属性替换该属性。", false)]
            public bool IsValid => IsAlive;

            public bool IsNull { get { return _dispatcher == null || _dispatcher.mDisposed; } }

            public CombinedHandler(string info)
            {
                lock (sync_lock)
                {
                    if (sSchedule == null)
                        throw new System.Exception("ParallelDispatcher was not initialized.");
                    _dispatcher = sSchedule;
                }
                this.info = info;
                this._cmd = null;
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                this._id = 0;
#endif
            }

            public Handler GetHandler()
            {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                if (_cmd != null && _cmd.id == _id)
                    return new Handler(_dispatcher, _cmd);
                else
                    return default;
#else
                return new Handler(_dispatcher, _cmd);
#endif
            }

            public void AbortAll(bool includeAllDependent = false)
            {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                if (_dispatcher != null && _cmd != null && _cmd.id == _id && !_cmd.isComplete)
#else
                if (_dispatcher != null && _cmd != null && !_cmd.isComplete)
#endif
                {
                    //_dispatcher.Abort(_cmd, false, includeAllDependent);
                    if (includeAllDependent)
                    {
                        _dispatcher.Abort(_cmd, false, true);
                    }
                    else
                    {
                        var set = new HashSet<BaseCommand>();
                        _cmd.GetAllJoinedCommands(set, false);
                        foreach (var job in set)
                        {
                            _dispatcher.Abort(job, job != _cmd, false);
                        }
                    }
                }
            }

            public void Combine(Handler handle)
            {
                if (_dispatcher == null)
                {
                    throw new System.Exception("Combined Handler was not initialized (combine parallel cmds).");
                }

                lock (_dispatcher.mLock)
                {
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                    var cmd = _cmd != null && _cmd.id == _id && !_cmd.isComplete ? _cmd : null;
#else
                    var cmd = _cmd == null || _cmd.isComplete ? null : _cmd;
#endif
                    var joinCmd = handle.cmd;
                    if (joinCmd == null || joinCmd == cmd || joinCmd.isComplete)
                        return;
#if CHECK_CMD_RECURSIVE
                    if (cmd != null)
                    {
                        if (schedule.IsDependentOn(cmd, joinCmd))
                            return;
                        if (schedule.IsDependentOn(joinCmd, cmd))
                            throw new System.Exception(string.Format("合并命令({0})已经依赖于被合并目标({1}),这将导致死锁", handle.cmd, _cmd));
                    }
#endif
                    if (cmd != null)
                    {
                        cmd.Join(handle);
                    }
                    else
                    {
                        var id = _dispatcher.NextID();
#if CACHE_COMMANDS && !DISABLE_CACHE_COMMANDS
                        _id = id;
                        _cmd = _dispatcher.mWaitCmdsBuffer.Get(id, null, false, joinCmd);
#else
                        _cmd = new WaitCmd(id, joinCmd);
#endif
#if PROFILING
                        _cmd.SetInfo(info);
#endif
                        _dispatcher.mCommands.Add(_cmd);
#if PROFILER_MODULE
                        ParallelProfiler.ScheduleCommandsCount.Value = _dispatcher.mCommands.Count;
#endif
                    }
                }
            }

            public override string ToString()
            {
                if (string.IsNullOrEmpty(info))
                    return "combined parallel cmd(s)";
                else
                    return info;
            }
        }

    }
}
//       